

package daa.javacard;

import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;

public class DaaApplet extends Applet {

	/*
	 ******************* Definition of constants *******************
	 */
	
	private final static byte INS_JOIN_SPK 		= (byte) 0x01;
	private final static byte INS_JOIN_SETV 	= (byte) 0x02;
	// INS BYTES - protocol commands
	private final static byte INS_SPK_REQ 		= (byte) 0x10;
	private final static byte INS_STEP_20 		= (byte) 0x20;
	private final static byte INS_STEP_21 		= (byte) 0x21;
	private final static byte INS_STEP_22 		= (byte) 0x22;
	
	private final static byte INS_HEXP_REQ 		= (byte) 0x30;
	private final static byte INS_HEXP_01 		= (byte) 0x31;
	// INS BYTES - applet initialisation and debugging commands
	private final static byte INS_SET_KEY 		= (byte) 0xF1;
	private final static byte INS_GET_KEY 		= (byte) 0xF2;
	private final static byte INS_GET_INTER 	= (byte) 0xF3;
	private final static byte INS_SET_INTER 	= (byte) 0xF4;
	private final static byte FIX_RAND 			= (byte) 0xFE;
	private final static byte INS_LOCK 			= (byte) 0xFF;
	
	// PARAMETER VALUES (GET/SET) - Issuer public key
	private final static byte SET_GET_R 		= (byte) 0x01;
	private final static byte SET_GET_S 		= (byte) 0x02;
	private final static byte SET_GET_N 		= (byte) 0x03;
	// PARAMETER VALUES (GET/SET) - Card secret key
	private final static byte SET_GET_F 		= (byte) 0x11;
	private final static byte SET_GET_V 		= (byte) 0x12;
	// PARAMETER VALUES (GET/SET) - Intermediate values
	private final static byte SET_GET_RV 		= (byte) 0x01;
	private final static byte SET_GET_RF 		= (byte) 0x02;
	private final static byte SET_GET_NT 		= (byte) 0x03;
	
	// VARIABLE LENGTHS (in number of bytes)
	private final static byte 	LENGTH_F		= 20;
	private final static short 	LENGTH_V 		= 189;
	private final static short 	LENGTH_R 		= 128;
	private final static short 	LENGTH_S 		= 128;
	private final static short 	LENGTH_N 		= 128;
	private final static byte 	LENGTH_PHI 		= 10;
	private final static byte 	LENGTH_H 		= 20;
	private final static short 	LENGTH_VPRIME	= LENGTH_N+LENGTH_PHI;
	private final static short 	LENGTH_RV 		= LENGTH_V + LENGTH_PHI + LENGTH_H; 	//219
	private final static byte 	LENGTH_RF 		= LENGTH_F + LENGTH_PHI + LENGTH_H;		//50
	private final static byte 	LENGTH_CH 		= 20;
	private final static byte 	LENGTH_M 		= 20;
	private final static byte 	LENGTH_NT 		= 10;
	private final static short 	LENGTH_TMP 		= 513;
	private final static short 	LENGTH_SV 		= LENGTH_RV + 1;	//max(LENGTH_RV, LENGTH_V+LENGTH_H)+1
	private final static short 	LENGTH_SF 		= LENGTH_RF + 1;	//max(LENGTH_RF, LENGTH_F+LENGTH_H)+1
	private final static short 	LENGTH_RSA 		= KeyBuilder.LENGTH_RSA_1024;
	private final static short 	LENGTH_RSA_BYTES= LENGTH_RSA/8;
	private final static short 	LENGTH_RV_EXP 	= LENGTH_RV - LENGTH_RSA_BYTES + 1;
	private final static short 	LENGTH_V_EXP 	= LENGTH_V - LENGTH_RSA_BYTES + 1;
	private final static byte 	LENGTH_D		= 20;
	
	
	// other constants
	private final static byte[] TWOa 			= {(byte)0x02};
	private final static byte 	ZERO 			= 0;
	private final static byte 	ONE 			= 1;
	private final static short 	MAX_BYTE 		= 0x00FF;

	
	private static byte[] rv_exp; 
	
	
	// declaration of persistent variables
	private byte[] f;
	private byte[] v;
	private byte[] R;
	private byte[] S;
	private byte[] n;
	private byte[] rv;
	private byte[] rf;
	private byte[] nt;
	
	
	// transient temporary byte array
	private byte[] temp;
	
	// built-in fuctionality variables
	//private RandomData rd;
	private MessageDigest md;
	private RSAPrivateKey privKey;
	private Cipher rsa;
	
	// other variables
	private boolean locked;
	private boolean randFixed;
	
	/**
	 * Install the applet
	 * 
	 * @param bArray the b array
	 * @param bOffset the b offset
	 * @param bLength the b length
	 */
	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new DaaApplet()
				.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
	}
	
	/**
	 * Instantiates a new daa applet. This method initialises the security parameters, a transient temporary byte array, the message digest instance, a RSA cipher
	 * instance and the secret value f.
	 */
	public DaaApplet(){
		// Initialisation of persistent variables (upon installation)
		f = new byte[LENGTH_F];
		v = new byte[LENGTH_V];
		R = new byte[LENGTH_R];
		S = new byte[LENGTH_S];
		n = new byte[LENGTH_N];
		rv = new byte[LENGTH_RV];
		rf = new byte[LENGTH_RF];
		nt = new byte[LENGTH_NT];
		
		locked = false;
		
		// Initialisation of transient temporary byte array
		temp = JCSystem.makeTransientByteArray(LENGTH_TMP, JCSystem.CLEAR_ON_DESELECT);
		
		// Message Digest initialisation
		md = MessageDigest.getInstance(MessageDigest.ALG_SHA, false);
		// Random data generator initialisation
		//rd = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
		// RSA initialisations
		privKey = (RSAPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE,LENGTH_RSA,false);
		rsa = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
		// 
		rv_exp = new byte[LENGTH_RV_EXP];
		rv_exp[0] = (byte)0x01;
		
		// Generate random f
		//rd.generateData(f, ZERO, LENGTH_F);
		//TODO: reset previous to random f!!!
		Util.arrayFillNonAtomic(f, ZERO, LENGTH_F, (byte)0xAA);
	}

	/**
	 * Process method: command dispatcher.
	 */
	public void process(APDU apdu) {
		// Return 9000 on applet selection
		if (selectingApplet()) {
			return;
		}

		byte[] buf = apdu.getBuffer(); // create a reference to the apdu buffer
		
		switch (buf[ISO7816.OFFSET_INS]) {
		// join protocol commands
		case INS_JOIN_SPK:
			joinRequest(apdu);
			break;
		case INS_JOIN_SETV:
			setCredential(apdu);
			break;
		
		// signing protocol commands
		case INS_SPK_REQ:
			spkRequest(apdu);
			break;
		case INS_STEP_20:
			hashRequest_0(apdu);
			break;
		case INS_STEP_21:
			hashRequest_1(apdu);
			break;
		case INS_STEP_22:
			hashRequest_2(apdu);
			break;
			
		// Hash exponentiation commands
		case INS_HEXP_REQ:
			hashExpRequest(apdu);
			break;
		case INS_HEXP_01:
			hashExpGet_01(apdu);
			break;
		
		// applet initialisation and debugging commands
		case INS_SET_KEY:
			setKeyValue(apdu);
			break;
		case INS_GET_KEY:
			getKeyValue(apdu);
			break;
		case INS_GET_INTER:
			getIntermediateValue(apdu);
			break;
		case INS_SET_INTER:
			setIntermediateValue(apdu);
			break;
		case FIX_RAND:
			fixRandomness(apdu);
			break;
		case INS_LOCK:
			lock(apdu);
			break;
			
		// good practice: If you don't know the INStruction, say so:
		default:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
	}
	
		
	/**
	 * Method called for generating the proof U while getting the credential. 
	 * 
	 * @param apdu APDU
	 */
	private void joinRequest(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();

		// fill v with zeros
//		Util.arrayFillNonAtomic(v, ZERO, LENGTH_V, ZERO);
//		// generate random v' at the correct place in v.
//		rd.generateData(v, (short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME);
		Util.arrayFillNonAtomic(temp, ZERO, LENGTH_V, ZERO);
		
		//if(!randFixed){
		//	rd.generateData(temp, (short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME);
			
		//}
		//else{
		//	Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, temp, (short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME);
			
		//}
		//TODO: reset previous to random v_prime (full if / else loop)!!!
		Util.arrayFillNonAtomic(temp,(short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME, (byte)0xBB);
		
		
		Util.arrayCopyNonAtomic(temp, ZERO, v, ZERO, LENGTH_V);
		
		// compute the multi-exponentiation
		expModN(S, ZERO, v, ZERO, LENGTH_RSA_BYTES, n, ZERO, temp, LENGTH_N);					// S^rv1 mod n -> temp[LENGTH_N..2*LENGTH_N-1]		
		expMod(temp, LENGTH_N, rv_exp, ZERO, LENGTH_V_EXP, temp, ZERO);							// S^(2^x*rv1) mod n -> temp[0..LENGTH_N-1]
		expMod(S, ZERO, v, LENGTH_RSA_BYTES, (short)(LENGTH_V_EXP-1), temp, LENGTH_N);		 	// S^rv2 mod n -> temp[LENGTH_N..2*LENGTH_N-1]
	
		// modulo multiplication: (S^rv1 * S^rv2) mod n	
		modMul(temp, ZERO, temp, LENGTH_N, n, ZERO, LENGTH_N, temp, (short)(2*LENGTH_N), (short)(3*LENGTH_N+1));
		
		// last part of the multi-exponentiation
		expMod(R, ZERO, f, ZERO, LENGTH_F, temp, (LENGTH_N));							// R^rf mod n -> temp[LENGTH_N+1..2*LENGTH_N]
		
		// modulo multiplication: [R^rf * (S^rv1 * S^rv2 * r^-1 * r^3 * r^-1)] * r^-1 mod n 
		modMul(temp, (short)(2*LENGTH_N+1), temp, (LENGTH_N), n, ZERO, LENGTH_N, buf, ZERO, (short)(3*LENGTH_N+1));
		
		// send back the result
		apdu.setOutgoingAndSend((short)1, LENGTH_N);
	}
	
	/**
	 * Method for getting the secret v'' and generating the secret v = v' + v'' at the enf of the Join Protocol
	 */
	private void setCredential(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
				
	//	addEq(v, (short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME, buf, ISO7816.OFFSET_CDATA, (short) ((0x00FF)&buf[ISO7816.OFFSET_LC]));
		
		add(v, (short)(LENGTH_V-LENGTH_VPRIME), LENGTH_VPRIME, buf, ISO7816.OFFSET_CDATA, (short) ((0x00FF)&buf[ISO7816.OFFSET_LC]), temp, ZERO);
		Util.arrayCopyNonAtomic(temp, ONE, v, ZERO, LENGTH_V);
		
		return;
	}
	
	byte ret,ret2;
	/**
	 * This method generates hatT1t in the begining of the Sign Protocol and sends it to the user.
	 * 
	 * @param apdu APDU
	 */
	private void spkRequest(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		if(!randFixed){
		// generate random r_v and r_f
		//rd.generateData(temp, ZERO, LENGTH_RV);
		//Util.arrayCopyNonAtomic(temp, ZERO, rv, ZERO, LENGTH_RV);
		//rd.generateData(temp, ZERO, LENGTH_RF);
		//Util.arrayCopyNonAtomic(temp, ZERO, rf, ZERO, LENGTH_RF);
		//TODO: reset previous to random r_v!!!
		Util.arrayFillNonAtomic(rv, ZERO, LENGTH_RV, (byte)0xCC);
		//TODO: reset previous to random rf!!!
		Util.arrayFillNonAtomic(rf, ZERO, LENGTH_RF, (byte)0xDD);
		
//		rd.generateData(rv, ZERO, LENGTH_RV);
//		rd.generateData(rf, ZERO, LENGTH_RF);
		}
		
		// compute the multi-exponentiation
		//expModN(S, ZERO, rv, ZERO, LENGTH_RSA_BYTES, n, ZERO, temp, LENGTH_N);				// S^rv1 mod n -> temp[LENGTH_N..2*LENGTH_N-1]		
		expMod(S, ZERO, rv, ZERO, LENGTH_RSA_BYTES, temp, LENGTH_N);				// S^rv1 mod n -> temp[LENGTH_N..2*LENGTH_N-1]		
		expMod(temp, LENGTH_N, rv_exp, ZERO, LENGTH_RV_EXP, temp, ZERO);					// S^(2^x*rv1) mod n -> temp[0..LENGTH_N-1]
		expMod(S, ZERO, rv, LENGTH_RSA_BYTES, (short)(LENGTH_RV_EXP-1), temp, LENGTH_N);	// S^rv2 mod n -> temp[LENGTH_N..2*LENGTH_N-1]
	
		// modulo multiplication: (S^rv1 * S^rv2) mod n	
		modMul(temp, ZERO, temp, LENGTH_N, n, ZERO, LENGTH_N, temp, (short)(2*LENGTH_N), (short)(3*LENGTH_N+1));
		
		// last part of the multi-exponentiation
		expMod(R, ZERO, rf, ZERO, LENGTH_RF, temp, (LENGTH_N));					// R^rf mod n -> temp[LENGTH_N+1..2*LENGTH_N]
		
		// modulo multiplication: [R^rf * (S^rv1 * S^rv2 * r^-1 * r^3 * r^-1)] * r^-1 mod n 
		modMul(temp, (short)(2*LENGTH_N+1), temp, (LENGTH_N), n, ZERO, LENGTH_N, buf, ZERO, (short)(3*LENGTH_N+1));
		
		// send back the result
		apdu.setOutgoingAndSend((short)1, LENGTH_N);
	}
	
	
	/**
	 * This method gets the challenge c_h and a message m from the host, computes a new challenge c, generates a nonce n_t and finally it sends c and n_t to the host.
	 * 
	 * @param apdu APDU containing c_h and m
	 */
	private void hashRequest_0(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		//Util.arrayCopyNonAtomic(buf, (short)(ISO7816.OFFSET_CDATA+LENGTH_CH), temp, ZERO, (short)(LENGTH_M+LENGTH_NT));
		Util.arrayCopyNonAtomic(buf, (short)(ISO7816.OFFSET_CDATA+LENGTH_CH), temp, ZERO, (short)(LENGTH_M));
		
		
		md.reset();
		md.update(buf, ISO7816.OFFSET_CDATA, LENGTH_CH);					// feed c_h to the hash function
		// generate the random n_t
		//rd.generateData(buf, ZERO, LENGTH_NT);
		//TODO: reset previous to random n_t!!!
		Util.arrayFillNonAtomic(buf, ZERO, LENGTH_NT, (byte)0xEE);
		
		md.doFinal(buf, ZERO, LENGTH_NT, buf, LENGTH_NT);					// feed n_t to the hash function and finalise it
		
		md.reset();
		md.update(buf, LENGTH_NT, LENGTH_H);								// feed the first hash result into the new hash function
		md.doFinal(temp, ZERO, LENGTH_M, buf, LENGTH_NT);					// feed m into the hash function and finalise it
		// result until here: (n_t || c) in buf

		// copy c locally
		Util.arrayCopy(buf, LENGTH_NT, temp, ZERO, LENGTH_H);
		
		// send the result
		apdu.setOutgoingAndSend(ZERO, (short)(LENGTH_NT + LENGTH_H));
	}
	
	/**
	 * This method is the responde to the GetSv() command in the host. The applat provides this value to the host.
	 * 
	 * @param apdu APDU
	 */
	private void hashRequest_1(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		// compute s_v = r_v + c*v
//		Util.arrayFillNonAtomic(buf, ZERO, (short)10, (byte)0x00);
//		multiply2(v, ZERO, LENGTH_V, temp, ZERO, LENGTH_H, buf, (short)11, LENGTH_H);
//		addEq(buf, (short)1, (short)(LENGTH_RV), rv, ZERO, LENGTH_RV);
		
		short outLen;
		outLen = multiply(v, ZERO, LENGTH_V, temp, ZERO, LENGTH_H, temp, LENGTH_H);
		outLen = add(temp, LENGTH_H, outLen, rv, ZERO, LENGTH_RV, buf, (short)0);
		
		//send s_v
		apdu.setOutgoingAndSend((short) 0, LENGTH_SV);
	}
	
	/**
	 * Method for getting s_f.
	 * 
	 * @param apdu APDU
	 */
	private void hashRequest_2(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();

		//short outLen;
		// compute s_f = r_f + c*f
//		outLen = multiply(f, ZERO, LENGTH_F, temp, ZERO, LENGTH_H, temp, (short)LENGTH_H);
//		outLen = add(temp, LENGTH_H, outLen, rf, ZERO, LENGTH_RF, buf, (short)0);
		Util.arrayFillNonAtomic(buf, ZERO, (short)10, (byte)0x00);
		multiply2(f, ZERO, LENGTH_F, temp, ZERO, LENGTH_H, buf, (short)11, LENGTH_H);
		addEq(buf, (short)1, (LENGTH_RF), rf, ZERO, LENGTH_RF);
		
		// send s_f
		apdu.setOutgoingAndSend((short) 0, LENGTH_SF);
	}
	
	/**
	*	Method for generating the pseudonim of the user.
	*
	*/
	private void hashExpRequest(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		// copy d to temp (at the correct place: zero padding!)
		Util.arrayFillNonAtomic(temp, ZERO, LENGTH_N, ZERO);
		Util.arrayCopyNonAtomic(buf, (ISO7816.OFFSET_CDATA), temp, (short)(LENGTH_N-LENGTH_D), LENGTH_D);
		
		// d^f mod n
		expModN(temp, ZERO, f, ZERO, LENGTH_F, n, ZERO, buf, ZERO);					// S^rv1 mod n -> temp[LENGTH_N..2*LENGTH_N-1]		
		
		// send back the result
		apdu.setOutgoingAndSend((short)0, LENGTH_N);
		
	}
	
	/**
	*	Method for generating the proof of the pseudonim on the user.
	*
	*/
	private void hashExpGet_01(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();

		// d^r_f mod n
		expMod(temp, ZERO, rf, ZERO, LENGTH_RF, buf, ZERO);					// S^rv1 mod n -> temp[LENGTH_N..2*LENGTH_N-1]		
	
		// send back the result		
		apdu.setOutgoingAndSend((short)0, LENGTH_N);
		
	}
	
	
	
	/**
	 * Modular exponentiation WITH setting of the modulus.
	 * Input values need to have the correct (RSA) length (except for the exponent)
	 * 
	 * @param base the base
	 * @param baseOffset the base offset
	 * @param exp the exp
	 * @param expOffset the exp offset
	 * @param expLen the exp len
	 * @param mod the mod
	 * @param modOffset the mod offset
	 * @param outBuff the out buff
	 * @param outOffset the out offset
	 * 
	 * @return the short
	 */
	private short expModN(byte[] base, short baseOffset, 
			byte[] exp, short expOffset, short expLen,
			byte[] mod, short modOffset,
			byte[] outBuff, short outOffset){
		
		// set the exponent
		privKey.setExponent(exp, expOffset, expLen);
		// set the modulus
		privKey.setModulus(mod, modOffset, LENGTH_RSA_BYTES);
		
		// (re-)initialise rsa
		rsa.init(privKey, Cipher.MODE_DECRYPT);
		// calculate the exponentiation result
		return rsa.doFinal(base, baseOffset, LENGTH_RSA_BYTES, outBuff, outOffset);
	}
	
	
	/**
	 * Modular exponentiation WITHOUT setting the modulus (uses the previously set mudulus through expModN(...))
	 * 
	 * @param base the base
	 * @param baseOffset the base offset
	 * @param exp the exponent
	 * @param expOffset the exponent offset
	 * @param expLen the exponent len
	 * @param outBuff the output buffer
	 * @param outOffset the output buffer offset
	 * 
	 * @return the length of the result
	 */
	private short expMod(byte[] base, short baseOffset, 
			byte[] exp, short expOffset, short expLen,
			byte[] outBuff, short outOffset){
		
		// set the exponent
		privKey.setExponent(exp, expOffset, expLen);
		
		// (re-)initialise rsa
		rsa.init(privKey, Cipher.MODE_DECRYPT);
		// calculate the exponentiation result
		return rsa.doFinal(base, baseOffset, LENGTH_RSA_BYTES, outBuff, outOffset);
	}
	
	
	/**
	 * Compare two byte arrays
	 * 
	 * @return 	 0 if src1 == src2
	 * 			 1 if src1 > src2
	 * 			-1 if src1 < src2
	 */
	private boolean compare(byte[] src1, short src1Off, byte[] src2, short src2Off, short length){
		if(src1[src1Off]!=ZERO){
			return true;
		}
		
		if(aCompare(src1, (short)(src1Off+1), src2, src2Off, length)>=0){
			return true;
		}
		return false;
	}
	
	private byte aCompare(byte[] src1, short src1Off, byte[] src2, short src2Off, short length){
		short i, a, b;
		for (i = 0; i < length; i++) {
			a = (short) ((0x00FF)&src1[(short)(src1Off+i)]);
			b = (short) ((0x00FF)&src2[(short)(src2Off+i)]);
			if (a > b) {
				return 1;
			}
			if (a < b) {
				return -1;
			}
		}
		return 0;	
	}
	
	/**
	 * Modular multiplication of two big number byte arrays
	 * method: ((a+b)^2 -a^2 - b^2) >> 1 == a*b
	 * 
	 * Needs space in the temp array! (length+1)
	 * 
	 * @param src1 the source for the first number
	 * @param src1Off the offset for src1
	 * @param src2 the source for the second number
	 * @param src2Off the offset for src2
	 * @param src3 the source for the modulo value m
	 * @param src3Off the offset for src3
	 * @param length the length of all the values
	 * @param dest the destination for the result
	 * @param destOff the offset for the destination
	 */
	private void modMul(byte[] src1, short src1Off, byte[] src2, short src2Off, byte[] src3, short src3Off, short length, byte[] dest, short destOff, short tempOff){
		// dest = src1 + src2
		add(src1, src1Off, length, src2, src2Off, length, dest, destOff);				if(ret2 == (short)1) return;
		
		// dest = src1 + src2 (mod n)
		if(compare(dest, (destOff), src3, src3Off, length)){
			subtractEq(dest, destOff, (short)(length+1), src3, src3Off, length);
		}																				if(ret2 == (short)2) return;
		
		// temp = dest^2 = (src1 + src2)^2 (mod n)
		expMod(dest, (short)(destOff+1), TWOa, ZERO, ONE, dest, (short)(destOff+1));				if(ret2 == (short)3) return;
		
		// dest = temp + 2n
		//addMultiple(temp, tempOff, src3, src3Off, length, (byte)2, dest, destOff);		if(ret2 == (short)4) return;
		
		// temp = src1^2 (mod n)
		expMod(src1, src1Off, TWOa, ZERO, ONE, temp, tempOff);							if(ret2 == (short)5) return;
		
		// dest = dest - temp = (src1 + src2)^2 (mod n) + 2n - src1^2 (mod n)
		if(!compare(dest, destOff, temp, tempOff, length)){
			addEq(dest, destOff, (short)(length+1), src3, src3Off, length);
		}
		subtractEq(dest, destOff, (short)(length+1), temp, tempOff, length);			if(ret2 == (short)6) return;
		
		// temp = src2^2 (mod n)
		expMod(src2, src2Off, TWOa, ZERO, (short)1, temp, tempOff);						if(ret2 == (short)7) return;
		
		// dest = dest - temp = (src1 + src2)^2 (mod n) + 2n - src1^2 (mod n) - src2^2 (mod n)
		if(!compare(dest, destOff, temp, tempOff, length)){
			addEq(dest, destOff, (short)(length+1), src3, src3Off, length);
		}
		subtractEq(dest, destOff, (short)(length+1), temp, tempOff, length);			if(ret2 == (short)8) return;
		
		
		// dest = dest >> 1 (mod n)
		// last bit == 0 -> shift right 1 bit
		if(((0x0001) & dest[(short)(destOff+length)]) != 0){
			// last bit == 1 & tmp>=n -> tmp-n & shift right 1 bit						
																						if(ret2 == (short)9) return;
			if(compare(dest, destOff, src3, src3Off, length)){
				subtractEq(dest, destOff, (short)(length+1), src3, src3Off, length);
			}
			// last bit == 1 & tmp<n -> tmp+n & shift right 1 bit
			else{
				addEq(dest, destOff, (short)(length+1), src3, src3Off, length);			
			}
		}
		// shift right 1 bit
		shiftRight(dest, destOff, (short)(length+1));									if(ret2 == (short)10) return;
		
		// while(tmp>n) subtract n
		while(compare(dest, destOff, src3, src3Off, length)){
			subtractEq(dest, destOff, (short)(length+1), src3, src3Off, length);		
		}																				if(ret2 == (short)11) return;
	}
	
	

	
	/**
	 * Shift right by one bit
	 */
	private void shiftRight(byte[] src, short srcOff, short length){
		short i = 0;
		//byte carry = 0;
		short ind, indBase;
		indBase = (short) (srcOff+length);
		
		for(i=1 ; i<length ; i++){
			ind = (short) (indBase-i);
			//carry = (byte) (((0x01)&(src[ind-1]))<<7);
			//src[ind] = (byte) (((0x007F)&(src[ind] >>> 1))|carry);
			//carry = (byte) (((0x01)&(src[ind-1]))<<7);
			src[ind] = (byte) (((0x007F)&(src[ind] >>> 1))|(((0x01)&(src[(short)(ind-1)]))<<7));
		}
		src[srcOff] = (byte) ((0x007F)&(src[srcOff] >>> 1));	
	}
	
	/**
	 * Adds two big number byte arrays
	 * 
	 * @param src1 the source 1
	 * @param src1Off the offset for source 1
	 * @param length1 the length of source 1
	 * @param src2 the source 2
	 * @param src2Off the offset for source 2
	 * @param length2 the length of source 2
	 * @param dest the destination for the result
	 * @param destOff the destination offset
	 * 
	 * @return the length of the result
	 */
	private short add(byte[] src1, short src1Off, short length1, byte[] src2, short src2Off, short length2, byte[] dest, short destOff){	
		short i, outLen;
		short w=0;
		// set outLen
		if (length1 <= length2) {
			outLen = (short) (length2 + 1);
		} else {
			outLen = (short) (length1 + 1);
		}
		// index bases
		short indBase1, indBase2, indBase3;
		indBase1 = (short)(src1Off + length1-1);
		indBase2 = (short)(src2Off + length2-1);
		indBase3 = (short)(destOff + outLen-1);
		
		// addition loop
		for (i = 0; i < (short)(outLen - 1); i++) {
			if (i < length1) {
				w += MAX_BYTE & (src1[(short)(indBase1 - i)]);
			}
			if (i < length2) {
				w += MAX_BYTE & (src2[(short)(indBase2 - i)]);
			}
			dest[(short)(indBase3 - i)] = (byte) w;
			w = (byte) ((w >>> 8) & 0x0001);
		}
		
		dest[destOff] = (byte)w;
		return outLen;
		
	}
	
	/**
	 * addEq
	 * operation +=
	 * result:  src1 += src2
	 * 				  = src1 + src2
	 */
	private short addEq(byte[] src1, short src1Off, short length1, byte[] src2, short src2Off, short length2){	
		short i, outLen;
		short w=0;
		// index bases
		short indBase1, indBase2;
		indBase1 = (short)(src1Off + length1-1);
		indBase2 = (short)(src2Off + length2-1);

		// set outLen
		if (length1 <= length2) {
			outLen = (short) (length2 + 1);
		} else {
			outLen = (short) (length1 + 1);
		}
		
		// addition loop
		for (i = 0; i < (short)(outLen - 1); i++) {
			if (i < length1) {
				w += MAX_BYTE & (src1[(short)(indBase1 - i)]);
			}
			if (i < length2) {
				w += MAX_BYTE & (src2[(short)(indBase2 - i)]);
			}
			src1[(short)(indBase1 - i)] = (byte) w;
			w = (byte) ((w >>> 8) & 0x0001);
		}
		
		if(w != 0){
			src1[(short)(src1Off + length1-outLen)] = (byte)w;
		}
		return outLen;
	}

	
	/**
	 * addMultiple
	 * adds a multiple of src2 to src1 and puts the result in dest
	 * @param src1
	 * @param src2
	 * @param multiple: the multiple of src2 to be added (maximum 255)
	 */ 
//	private void addMultiple(byte[] src1, short src1Off, byte[] src2, short src2Off, short length, byte multiple, byte[] dest, short destOff){
//		short i, wA, wM, carryM;
//		i = 0; wA = 0; wM = 0; carryM=0;
//		
//		for(i = 0; i<length; i++){
//			wM = 0;
//			
//			// multiply the current src2 byte by multiple
//			wM = (short) (MAX_BYTE & src2[(short)(src2Off + length -1 - i)]);
//			wM *= MAX_BYTE&multiple;
//			
//			// add the multiplication carry byte
//			wM += MAX_BYTE&carryM;
//			
//			// set the new multiplication carry byte (the upper part of wM) 
//			carryM = (short)((wM >>> 8) & 0x00FF);
//		
//			// add the current src1 byte to the multiplied byte and the previous addition carry byte (bit)
//			wA += (short) (MAX_BYTE & src1[(short)(src1Off + length -1 - i)]);
//			wA += (short)(MAX_BYTE & wM);
//			
//			// write the lower byte of the result into the destination byte array
//			dest[(short)(destOff + length - i)] = (byte) (MAX_BYTE&wA);
//			
//			// put the new addition carry byte (bit) in wA
//			wA = (short)((wA >>> 8) & 0x00FF);
//		}
//		dest[destOff] = (byte)wA;
//	}
	
		
	/**
	 * operation -=
	 * result:  src1 -= src2
	 * 			src1 = src1 - src2
	 */
	private void subtractEq(byte[] src1, short src1Off, short length1, byte[] src2, short src2Off, short length2){
		short i = 0;
		short w = 0;
		short indBase1 = (short) (src1Off + length1 - 1);
		short indBase2 = (short) (src2Off + length2 - 1);
		
		for (i = 0; i < length1; i++) {
			w += (short) (MAX_BYTE & src1[(short)(indBase1 - i)]);
			if(i<length2){
				w = (short) (w-(MAX_BYTE&src2[(short)(indBase2 - i)]));
			}
			src1[(short)(indBase1 - i)] = (byte) (MAX_BYTE&w);
			if (w < 0){ 
				w = -1; 
			}
			else {
				w = 0;
			}
		}
	}
		
	
	/**
	 * Multiplies two big number byte arrays
	 * 
	 * @param src1 the source 1
	 * @param src1Off the offset for source 1
	 * @param length1 the length of source 1
	 * @param src2 the source 2
	 * @param src2Off the offset for source 2
	 * @param length2 the length of source 2
	 * @param dest the destination for the result
	 * @param destOff the destination offset
	 * 
	 * @return the length of the result
	 */
	private short multiply(byte[] src1, short src1Off, short length1, byte[] src2, short src2Off, short length2, byte[] dest, short destOff){
		byte carry;
		short w, i, j, outLen;
		w = 0;
		carry = 0;
		outLen = (short) (length1 + length2);
		Util.arrayFillNonAtomic(dest, destOff, outLen, (byte)0x00);
		for (i = (short) (length2-1); i >= 0; i--) {
			carry = 0;
			for (j = (short) (length1-1); j >= 0; j--) {			
				w = (short) ((short) (MAX_BYTE & (src1[(short)(src1Off + j)])) 
								* (short)(MAX_BYTE & (src2[(short)(src2Off + i)]))
								+ (short)(MAX_BYTE & (dest[(short)(destOff + i+j+1)]))
								+ (short)(MAX_BYTE & (carry))
				);
				
				dest[(short)(destOff + i+j+1)] = (byte) (w & 0x00FF);
				carry = (byte) ((w >>> 8) & 0x00FF);	
			}
			dest[(short)(destOff + i)] = carry;
		}
		
		return outLen;
	}
		
	
	private void multiply2(byte[] src1, short src1Off, short length1, byte[] src2, short src2Off, short length2, byte[] dest, short destOff, short tempOff){
	
		short i = 1;
		short tmpOff0 = tempOff;				short tLen0 = 40;
		short tmpOff1 = (short)(tmpOff0+tLen0);	short tLen1 = 128;
		short tmpOff2 = (short)(tmpOff1+tLen1);	short tLen2 = 128;
		short tmpOff3 = (short)(tmpOff2+tLen2);	short tLen3 = 128;
		
		
		
		// tmp0 = c^2
		Util.arrayFillNonAtomic(temp, tmpOff0, (short)(tLen0+tLen1+tLen2+tLen3), (byte)0x00);
		Util.arrayCopyNonAtomic(src2, src2Off, temp, (short)(tmpOff1+tLen1-length2), length2);
		expMod(temp, tmpOff1, TWOa, ZERO, (short)1, temp, tmpOff2);
		Util.arrayCopyNonAtomic(temp, (short)(tmpOff2+tLen2-tLen0), temp, tmpOff0, tLen0);
		
		byte steps = (byte) (length1/63);
		byte lastStep = 63;
		if((short)(steps*63 - length1) != 0){
			lastStep = (byte) (length1 - steps*63);
			steps++;
		}
		
		// for i=1..3
		for(i = 1 ; i <=steps ; i++){
			// tmp1 = v_i
			Util.arrayFillNonAtomic(temp, tmpOff1, tLen1, (byte)0x00);
			if(i == steps){
				Util.arrayCopyNonAtomic(src1, (src1Off), temp, (short)(tmpOff1+tLen1-lastStep), lastStep);
			}
			else{
				Util.arrayCopyNonAtomic(src1, (short)(src1Off+length1-i*63), temp, (short)(tmpOff1+tLen1-63), (short)63);	
			}
					
			// c * tmp1
			{
				// tmp2 = tmp1^2
				expMod(temp, tmpOff1, TWOa, ZERO, (short)1, temp, tmpOff2);
				
				// tmp1 += c
				addEq(temp, tmpOff1, tLen1, src2, src2Off, length2);
				
				// tmp3 = tmp1^2
				expMod(temp, tmpOff1, TWOa, ZERO, (short)1, temp, tmpOff3);
				
				// tmp3 -= tmp2
				subtractEq(temp, tmpOff3, tLen3, temp, tmpOff2, tLen2);
				
				// tmp3 -= tmp0
				subtractEq(temp, tmpOff3, tLen3, temp, tmpOff0, tLen0);
				
				// tmp3 >> 1
				shiftRight(temp, tmpOff3, tLen3);
				
			}
			// copy tmp3(0..62) to dest(i*63..i*63+62)
			if(i == steps){
				addEq(dest, (short)(destOff+length1+length2-(i-1)*63-lastStep-length2-1), (short)(lastStep+length2+1), temp, (short)(tmpOff3+tLen3-lastStep-length2-1), (short)(lastStep+length2+1));
			}
			else{
				addEq(dest, (short)(destOff+length1+length2-i*63-length2-1), (short)(64+length2), temp, (short)(tmpOff3+tLen3-(64+length2)), (short)(64+length2));
			}		
		// end for
		}
		
		
	}
	
	/**
	 * Sets one of the issuer-public-key values.
	 * Only possible when the applet is not in 'locked' state.
	 * 
	 * @param apdu the new key value
	 */
	private void setKeyValue(APDU apdu) {
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		if (locked) {
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			return;
		}
		
		switch (buf[ISO7816.OFFSET_P1]) {
		case SET_GET_R:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, R, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_S:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, S, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_N:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, n, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_F:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, f, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_V:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, v, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}
	}
	
	/**
	 * Gets one of the issuer-public-key values.
	 * Only possible when the applet is not in 'locked' state.
	 * 
	 * @param apdu APDU
	 */
	private void getKeyValue(APDU apdu){
		byte[] buf = apdu.getBuffer();
		
		if (locked) {
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			return;
		}
		
		switch (buf[ISO7816.OFFSET_P1]) {
		case SET_GET_R:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_R);
			apdu.sendBytesLong(R, ZERO, LENGTH_R);
			return;
		case SET_GET_S:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_S);
			apdu.sendBytesLong(S, ZERO, LENGTH_S);
			return;
		case SET_GET_N:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_N);
			apdu.sendBytesLong(n, ZERO, LENGTH_N);
			return;
		case SET_GET_F:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_F);
			apdu.sendBytesLong(f, ZERO, LENGTH_F);
			return;
		case SET_GET_V:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_V);
			apdu.sendBytesLong(v, ZERO, LENGTH_V);
			return;
		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}
	}
	
	
	/**
	 * Gets one of the intermediate values.
	 * Only possible when the applet is not in 'locked' state.
	 * 
	 * @param apdu APDU
	 */
	private void getIntermediateValue(APDU apdu){
		byte[] buf = apdu.getBuffer();
		
		if (locked) {
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			return;
		}
		
		switch (buf[ISO7816.OFFSET_P1]) {
		case SET_GET_RV:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_RV);
			apdu.sendBytesLong(rv, ZERO, LENGTH_RV);
			return;
		case SET_GET_RF:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_RF);
			apdu.sendBytesLong(rf, ZERO, LENGTH_RF);
			return;
		case SET_GET_NT:
			apdu.setOutgoing();
			apdu.setOutgoingLength(LENGTH_NT);
			apdu.sendBytesLong(nt, ZERO, LENGTH_NT);
			return;
		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}
		
	}
	
	/**
	 * Sets one of the intermediate values.
	 * Only possible when the applet is not in 'locked' state.
	 * 
	 * @param apdu the apdu
	 */
	private void setIntermediateValue(APDU apdu){
		byte[] buf = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		
		if (locked) {
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			return;
		}
		
		switch (buf[ISO7816.OFFSET_P1]) {
		case SET_GET_RV:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, rv, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_RF:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, rf, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		case SET_GET_NT:
			Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, nt, ZERO, (short) (MAX_BYTE&buf[ISO7816.OFFSET_LC]));
			return;
		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}
		
	}
	
	/**
	 * Lock the key set. Getting or setting one of the key values is not possible anymore after calling this method.
	 * 
	 * @param apdu the apdu
	 */
	private void lock(APDU apdu){
		locked = true;
	}

	private void fixRandomness(APDU apdu){
		byte[] buf = apdu.getBuffer();
		
		if(buf[ISO7816.OFFSET_P1] == (byte)0x01){
			randFixed = true;
		}
		else{
			randFixed = false;
		}
	}
	
	
}
